package com.crayon2f.test.kit;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import lombok.Cleanup;
import lombok.SneakyThrows;
import org.apache.commons.lang3.StringUtils;

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Properties;

import static org.apache.commons.lang3.StringUtils.EMPTY;
import static org.apache.commons.lang3.StringUtils.isNotEmpty;

/**
 * Created by feifan.gou@gmail.com on 2018/6/19 13:54.
 * 根据数据库表生成 entity, vo, mapper
 */
public class GenerateBeanFromDbKit {


    public static void main(String[] args) {

        Generator generator = null;
        try {
            generator = new Generator("device_base_info");
//            generator.setVo(false);
            generator.generate();
        } catch (Exception e) {
            Optional.ofNullable(generator).ifPresent(Generator::cleanFile);
            e.printStackTrace();
        }
    }

    private final static class Generator {

        /* path constant list */
        private static final String BASE_PATH = System.getProperty("user.dir");
        private static final String VO_PATH = BASE_PATH + "/src/main/java/com/crayon2f/test/beans/";
        private static final String ENTITY_PATH = BASE_PATH + "/src/main/java/com/crayon2f/test/beans/";
        private static final String MAPPER_PATH = BASE_PATH + "/src/main/java/com/crayon2f/test/beans/";

        private static final String VO_PACKAGE = "com.crayon2f.test.beans";
        private static final String ENTITY_PACKAGE = "com.crayon2f.test.beans";
        private static final String MAPPER_PACKAGE = "com.crayon2f.test.beans";
        private static Path[] PATHS;

        /* db constant list */
        private static final String DB_NAME = "basedata";
        private static final String DB_URL = "jdbc:mysql://10.39.30.21:3306/" + DB_NAME;
        private static final String DB_USER = "basedata_test";
        private static final String DB_PASSWORD = "basedata_test";
        private static final Properties PROPERTIES = new Properties() {{
            put("useUnicode", "true");
            put("characterEncoding", "UTF-8");
            put("zeroDateTimeBehavior", "convertToNull");
            put("serverTimezone", "Asia/Shanghai");
            put("useSSL", "true");
            put("user", DB_USER);
            put("password", DB_PASSWORD);
        }};
        private static final String DB_DRIVER = "com.mysql.cj.jdbc.Driver";
        private static final String TAB = "    ";
        private static final String TAB_2 = TAB + TAB;
        private static String TABLE_NAME = "";
        private static String BUSINESS_NAME = "";
        private static String PACKAGE_BUSINESS_NAME = "";
        private static String TABLE_COMMENT = EMPTY;

        /* result var list */
        private static final List<Map<String, String>> FIELD_RESULT_LIST = new ArrayList<>();
        private static List<String> CONTENT_LIST;

        /* column constant list */
        private static final String FIELD = "field";
        private static final String KEY = "key";
        private static final String COMMENT = "comment";
        private static final String TYPE = "type";

        private boolean entity = true;
        private boolean vo = true;
        private boolean mapper = true;


        private Generator(String tableName) {
            this(tableName, EMPTY);
        }

        /**
         * 构造器
         *
         * @param tableName    表名
         * @param businessName 业务名,用于生成文件上级目录
         */
        private Generator(String tableName, String businessName) {

            TABLE_NAME = Optional.ofNullable(tableName)
                    .filter(StringUtils::isNotEmpty)
                    .orElseThrow(() -> new RuntimeException("table name can't be empty !"));
            BUSINESS_NAME = Optional.ofNullable(businessName).filter(StringUtils::isNotEmpty).orElse(EMPTY);
            PACKAGE_BUSINESS_NAME = BUSINESS_NAME.replace("/", ".");
            initDB();
            initPaths();
        }

        public void setEntity(boolean entity) {
            this.entity = entity;
        }

        public void setVo(boolean vo) {
            this.vo = vo;
        }

        public void setMapper(boolean mapper) {
            this.mapper = mapper;
        }

        /**
         * 获取数据库字段信息
         */
        @SneakyThrows
        private void initDB() {

            Class.forName(DB_DRIVER);
            @Cleanup
            Connection connection = Optional
                    .ofNullable(DriverManager.getConnection(DB_URL, PROPERTIES))
                    .orElseThrow(() -> new RuntimeException("can't get connection"));
            @Cleanup
            PreparedStatement statement = connection.prepareStatement(String.format("SHOW FULL COLUMNS FROM `%s`", TABLE_NAME));
            @Cleanup
            ResultSet fieldInfo = statement.executeQuery();
            while (fieldInfo.next()) {
                FIELD_RESULT_LIST.add(
                        ImmutableMap.of(
                                FIELD, fieldInfo.getString(FIELD),
                                KEY, fieldInfo.getString(KEY),
                                COMMENT, fieldInfo.getString(COMMENT),
                                TYPE, fieldInfo.getString(TYPE)
                        )
                );
            }
            @Cleanup
            ResultSet tableInfo = statement.executeQuery(String.format("show table status where `name` = '%s'", TABLE_NAME));
            while (tableInfo.next()) {
                TABLE_COMMENT = tableInfo.getString(COMMENT);
                if (TABLE_COMMENT.contains("表")) {
                    TABLE_COMMENT = TABLE_COMMENT.replace("表", EMPTY);
                }
            }
        }

        /**
         * 初始化java文件路径
         */
        private void initPaths() {

            String javaFileName = underline2hump(TABLE_NAME, true);
            String businessName = EMPTY;
            if (isNotEmpty(BUSINESS_NAME)) {
                businessName = BUSINESS_NAME + File.separator;
            }
            PATHS = new Path[]{
                    Paths.get(String.format("%s%s.java", ENTITY_PATH, javaFileName)),
                    Paths.get(String.format("%s%s%sVo.java", VO_PATH, businessName, javaFileName)),
                    Paths.get(String.format("%s%s%sMapper.java", MAPPER_PATH, businessName, javaFileName)),
            };
        }

        private String getClassName(Path path) {

            String fileNameStr = path.getFileName().toString();
            return fileNameStr.substring(0, fileNameStr.lastIndexOf("."));
        }

        /**
         * 下划线 ==> 驼峰
         *
         * @param underline      下划线
         * @param firstUppercase 开头是否大写
         * @return 驼峰
         */
        private String underline2hump(String underline, boolean firstUppercase) {

            StringBuilder builder = new StringBuilder();
            if (!underline.contains("_")) {
                if (firstUppercase) {
                    builder.append(underline.substring(0, 1).toUpperCase()).append(underline.substring(1));
                } else {
                    builder.append(underline);
                }
            } else {
                char[] chars = underline.toCharArray();
                boolean isUnderline = false;
                for (char ch : chars) {
                    if (Objects.equals(ch, "_".charAt(0))) {
                        isUnderline = true;
                        continue;
                    }
                    if (isUnderline) {
                        ch = String.valueOf(ch).toUpperCase().charAt(0);
                        isUnderline = false;
                    }
                    builder.append(ch);
                }
            }
            if (firstUppercase) {
                builder = new StringBuilder().append(builder.substring(0, 1).toUpperCase()).append(builder.substring(1));
            }
            return builder.toString().trim();
        }

        /**
         * 写入 entity
         */
        @SneakyThrows
        private void writeEntity() {

            String className = getClassName(PATHS[0]);
            CONTENT_LIST = Lists.newArrayList(
                    String.format("package %s;", ENTITY_PACKAGE),
                    EMPTY);
            if (this.vo) {
                CONTENT_LIST.add(String.format("import " + ENTITY_PACKAGE + ".%sVo;", isNotEmpty(PACKAGE_BUSINESS_NAME) ? PACKAGE_BUSINESS_NAME + "." + className : className));
            }
            CONTENT_LIST.addAll(Lists.newArrayList(
                    "import lombok.AllArgsConstructor;",
                    "import lombok.Builder;",
                    "import lombok.Getter;",
                    "import lombok.NoArgsConstructor;",
                    "import lombok.Setter;",
                    EMPTY,
                    "import javax.persistence.GeneratedValue;",
                    "import javax.persistence.Id;"
            ));
            if (this.vo) {
                CONTENT_LIST.add("import org.springframework.beans.BeanUtils;");
                CONTENT_LIST.add("import java.util.Optional;");
            }
            CONTENT_LIST.addAll(Lists.newArrayList(
                    EMPTY,
                    "/** ",
                    " * Created by generator on " + LocalDateTime.now(),
                    " */",
                    "@NoArgsConstructor",
                    "@AllArgsConstructor",
                    "@Builder",
                    "@Getter",
                    "@Setter",
                    String.format("public class %s extends BaseSerializable {", className),
                    EMPTY
            ));
            FIELD_RESULT_LIST.forEach(field -> {
                if (field.get(KEY).equalsIgnoreCase("PRI")) {
                    CONTENT_LIST.add(TAB + "@Id");
                    CONTENT_LIST.add(TAB + "@GeneratedValue(generator = \"JDBC\")");
                    CONTENT_LIST.add(TAB + "private Long id;");
                    return;
                }
                String javaType = columnType2javaType(field);
                CONTENT_LIST.add(TAB + String.format("private %s %s;",
                        javaType, underline2hump(field.get(FIELD), false)));
            });
            CONTENT_LIST.add(EMPTY);
            if (this.vo) {
                CONTENT_LIST.add(TAB + String.format("public %s(%sVo vo) {", className, className));
                CONTENT_LIST.add(EMPTY);
                CONTENT_LIST.add(TAB_2 + "Optional.ofNullable(vo).ifPresent(ths -> BeanUtils.copyProperties(ths, this));");
                CONTENT_LIST.add(TAB + "}");
            }
            CONTENT_LIST.add("}");

            generateJavaFile(PATHS[0]);
        }

        /**
         * 写入 vo
         */
        @SneakyThrows
        private void writeVo() {

            String voClassName = getClassName(PATHS[1]);
            String className = voClassName.substring(0, voClassName.lastIndexOf("Vo"));
            CONTENT_LIST = Lists.newArrayList(
                    "package " + VO_PACKAGE + (isNotEmpty(PACKAGE_BUSINESS_NAME) ? "." + PACKAGE_BUSINESS_NAME : EMPTY) + ";",
                    EMPTY,
                    String.format("import %s.%s;", ENTITY_PACKAGE, className),
                    "import io.swagger.annotations.ApiModel;",
                    "import io.swagger.annotations.ApiModelProperty;",
                    "import lombok.AllArgsConstructor;",
                    "import lombok.Builder;",
                    "import lombok.Getter;",
                    "import lombok.NoArgsConstructor;",
                    "import lombok.Setter;",
                    EMPTY,
                    "import org.springframework.beans.BeanUtils;",
                    EMPTY,
                    "import java.util.Optional;",
                    EMPTY,
                    "/** ",
                    " * Created by generator on " + LocalDateTime.now(),
                    " */",
                    "@NoArgsConstructor",
                    "@AllArgsConstructor",
                    "@Builder",
                    "@Getter",
                    "@Setter",
                    isNotEmpty(TABLE_COMMENT) ? String.format("@ApiModel(\"%s\")", TABLE_COMMENT) : "@ApiModel",
                    String.format("public class %s {", voClassName),
                    EMPTY
            );
            FIELD_RESULT_LIST.forEach(field -> {
                if (field.get(KEY).equalsIgnoreCase("PRI")) {
                    CONTENT_LIST.add(TAB + "@ApiModelProperty(\"ID\")");
                    CONTENT_LIST.add(TAB + "private Long id;");
                    CONTENT_LIST.add(EMPTY);
                    return;
                }
                if (isNotEmpty(field.get(COMMENT))) {
                    CONTENT_LIST.add(String.format("%s@ApiModelProperty(\"%s\")", TAB, field.get(COMMENT)));
                } else {
                    CONTENT_LIST.add(TAB + "@ApiModelProperty");
                }
                String javaType = columnType2javaType(field);
                CONTENT_LIST.add(TAB + String.format("private %s %s;",
                        javaType, underline2hump(field.get(FIELD), false)));
                CONTENT_LIST.add(EMPTY);
            });
            String shortDbName = TABLE_NAME.contains("_") ? TABLE_NAME.substring(TABLE_NAME.lastIndexOf("_") + 1) : TABLE_NAME.toLowerCase();
            CONTENT_LIST.add(TAB + String.format("public %s(%s %s) {", voClassName, className, shortDbName));
            CONTENT_LIST.add(EMPTY);
            CONTENT_LIST.add(TAB_2 + String.format("Optional.ofNullable(%s).ifPresent(ths -> BeanUtils.copyProperties(ths, this));", shortDbName));
            CONTENT_LIST.add(TAB + "}");
            CONTENT_LIST.add("}");

            generateJavaFile(PATHS[1]);
            CONTENT_LIST.clear();
        }

        /**
         * 写入 mapper
         */
        @SneakyThrows
        private void writeMapper() {

            String mapperClassName = getClassName(PATHS[2]);
            String className = mapperClassName.substring(0, mapperClassName.lastIndexOf("Mapper"));
            CONTENT_LIST = Lists.newArrayList(
                    String.format("package " + MAPPER_PACKAGE + "%s;", (isNotEmpty(PACKAGE_BUSINESS_NAME) ? "." + PACKAGE_BUSINESS_NAME : EMPTY)),
                    EMPTY,
                    "import com.enn.inteop.platform.config.MyMapper;",
                    String.format("import %s.%s;", ENTITY_PACKAGE, className),
                    EMPTY,
                    "/** ",
                    " * Created by generator on " + LocalDateTime.now(),
                    " */",
                    String.format("public interface %s extends MyMapper<%s> {}", mapperClassName, className)
            );
            generateJavaFile(PATHS[2]);
        }

        /**
         * 生成文件
         *
         * @param path 文件路径
         */
        @SneakyThrows
        private void generateJavaFile(Path path) {

            if (!Files.exists(path.getParent())) {
                Files.createDirectories(path.getParent());
            }
            Files.write(path, CONTENT_LIST);
            CONTENT_LIST.clear();
            System.out.println(String.format("generate success: [%s]", path));
        }

        /**
         * db column ==> java type
         *
         * @param field 字段
         * @return java type
         */
        @SneakyThrows
        private String columnType2javaType(Map<String, String> field) {

            String tempType = field.get(TYPE);
            String columnType = tempType;
            if (tempType.contains("(")) {
                columnType = tempType.substring(0, tempType.indexOf("("));
            }
            String javaType;
            switch (columnType) {
                case "tinyint":
                case "smallint":
                case "mediumint":
                case "int":
                    javaType = "Integer";
                    break;
                case "bigint":
                case "long":
                    javaType = "Long";
                    break;
                case "date":
                case "datetime":
                case "timestamp":
                    javaType = "java.util.Date";
                    break;
                case "float":
                case "double":
                case "decimal":
                    javaType = "Double";
                    break;
                case "char":
                case "varchar":
                case "tinytext":
                case "text":
                case "mediumtext":
                case "longtext":
                    javaType = "String";
                    break;
                default:
                    throw new RuntimeException(String.format("unknown column type [%s]", columnType));
            }
            return javaType;
        }

        private void generate() {

            if (this.entity) {
                writeEntity();
            }
            if (this.vo) {
                writeVo();
            }
            if (this.mapper) {
                writeMapper();
            }
        }

        /**
         * 清除生成的java文件
         */
        @SneakyThrows
        private void cleanFile() {

            for (Path path : PATHS) {
                if (Files.deleteIfExists(path)) {
                    System.out.println(String.format("delete file success: [%s]", path));
                }
                if (Objects.requireNonNull(path.getParent().toFile().listFiles()).length == 0) {
                    if (Files.deleteIfExists(path.getParent())) {
                        System.out.println(String.format("delete dir success: [%s]", path.getParent()));
                    }
                }
            }
        }
    }
}
